#![no_main]
#![no_std]

#[macro_use]
extern crate qemu_stm32_tests;

use qemu_stm32_tests::prelude::v1::*;

use qemu_stm32_tests::freertos_rs::patterns::compute_task::*;
use qemu_stm32_tests::freertos_rs::patterns::processor::*;
use qemu_stm32_tests::freertos_rs::patterns::pub_sub::*;

freertos_rs_test!(TestMemLeaks1);

pub struct TestMemLeaks1;
impl Test for TestMemLeaks1 {
	fn run<T: Testbed>(tb: &T) {
		let main_task = Task::new().name("main").stack_size(2048).start(|| {
			let start_memory_usage = T::heap_allocated_memory();
			let mut end_memory_usage = 0;

			// simple task spawn		
			for i in 0..100 {
				Task::new().name(&format!("t_{}", i)).stack_size(256).start(move || {
					let s = format!("Hello world {}", i);
				}).unwrap();

				CurrentTask::delay(Duration::ms(2));
			}
			
			// simple mutexes
			for i in 0..100 {
				let m = Mutex::new(0).unwrap();
				let mut v = m.lock(Duration::ms(50)).unwrap();
				*v += 1;
			}
			
			// recursive mutexes
			for i in 0..100 {
				let m = RecursiveMutex::new(0).unwrap();
				let mut v = m.lock(Duration::ms(50)).unwrap();
				*v += 1;
			}

			// deconstructing a mutex
			{
				let n = 50;

				let test_str = "Hello Heap World";

				let mutexes = {
					let mut v = vec![];
					for _ in 0..n {
						let mutex = {
							let m = Mutex::new(test_str.to_string()).unwrap();
							{
								let l = m.lock(Duration::ms(50)).unwrap();
								assert_eq!(test_str, *l);
							}
							m
						};

						v.push(mutex);
					}

					v
				};

				for mutex in mutexes.into_iter() {
					let inner: String = mutex.into_inner();
					assert_eq!(test_str, inner);
				}			
			}

			// simple queues
			for i in 0..100 {
				let q = Queue::new(10).unwrap();
				q.send(10, Duration::ms(5)).unwrap();
				q.receive(Duration::ms(100)).unwrap();
			}
			
			end_memory_usage = T::heap_allocated_memory();
			assert_eq!(start_memory_usage, end_memory_usage, "Mem usage #1");

			// compute tasks
			{
				let n = 12;
				let res: u64 = 42;

				let tasks = {
					let mut v = vec![];

					for i in 0..n {
						let name = format!("comp_{}", i);
						let t = Task::new().name(&name).stack_size(256).compute(|| {
							CurrentTask::delay(Duration::ms(200));
							42 as u64
						}).unwrap();
						v.push(t);
					}

					v
				};

				for task in tasks.into_iter() {
					let result = task.into_result(Duration::ms(200)).unwrap();
					assert_eq!(res, result);
				}

			}

			CurrentTask::delay(Duration::ms(200));

			end_memory_usage = T::heap_allocated_memory();
			assert_eq!(start_memory_usage, end_memory_usage, "Mem usage #2");

			// pub sub
			{
				let w = Duration::ms(1);

				let publisher = QueuePublisher::new().unwrap();			
				let sub1 = publisher.subscribe(10, w).unwrap();
				assert_eq!(1, publisher.send("A", w));
				let sub2 = publisher.subscribe(10, w).unwrap();			
				let publisher2 = publisher.clone();
				assert_eq!(2, publisher2.send("B", w));			
				
				assert_eq!("A", sub1.receive(w).unwrap());
				assert_eq!("B", sub1.receive(w).unwrap());
				assert_eq!(Result::Err(FreeRtosError::QueueReceiveTimeout), sub1.receive(w));
				drop(sub1);

				assert_eq!("B", sub2.receive(w).unwrap());
				assert_eq!(Result::Err(FreeRtosError::QueueReceiveTimeout), sub2.receive(w));
			}

			end_memory_usage = T::heap_allocated_memory();
			assert_eq!(start_memory_usage, end_memory_usage, "Mem usage #3");
			
			// timers		
			{
				let timer = Timer::new(Duration::ms(50))
					.set_auto_reload(false)
					.create(|mut timer| {                    
						let a = 1;
					}).unwrap();

				timer.start(Duration::infinite()).unwrap();

				CurrentTask::delay(Duration::ms(100))
			}

			end_memory_usage = T::heap_allocated_memory();
			assert_eq!(start_memory_usage, end_memory_usage, "Mem usage #4");

			// processor
			{
				#[derive(PartialEq, Copy, Clone, Debug)]
				enum ProcessorMsg {
					Val(usize),
					Shutdown
				}

				let processor: Processor<InputMessage<ProcessorMsg>, usize> = Processor::new(5).unwrap();
				let client_1 = processor.new_client().unwrap();
				let client_2 = processor.new_client_with_reply(5, Duration::ms(5)).unwrap();
				let client_3 = processor.new_client().unwrap();
				let client_4 = processor.new_client_with_reply(15, Duration::ms(5)).unwrap();

				let processor_task = Task::new().name("processor").start(move || {
					loop {
						if let Ok(m) = processor.get_receive_queue().receive(Duration::ms(10)) {
							match m.get_val() {
								ProcessorMsg::Val(v) => {
									let processed = v + 1;
									let r = processor.reply_val(m, processed, Duration::ms(10)).unwrap();
								},
								ProcessorMsg::Shutdown => { break; }
							}
						}
					}
					drop(processor);
					T::debug_print("Processor shutting down");
				}).unwrap();
				
				client_1.send_val(ProcessorMsg::Val(5), Duration::ms(5));
				client_2.send_val(ProcessorMsg::Val(6), Duration::ms(5));
				client_2.send_val(ProcessorMsg::Val(7), Duration::ms(5));
				client_2.call_val(ProcessorMsg::Val(8), Duration::ms(5));
				
				client_3.send_val(ProcessorMsg::Shutdown, Duration::ms(5));

				CurrentTask::delay(Duration::ms(50));
							
				assert_eq!(Err(FreeRtosError::ProcessorHasShutDown), client_4.call_val(ProcessorMsg::Val(2), Duration::ms(5)));
			}

			CurrentTask::delay(Duration::ms(300));		

			end_memory_usage = T::heap_allocated_memory();
			assert_eq!(start_memory_usage, end_memory_usage, "Mem usage final");

			T::ok();
		}).unwrap();

		T::start_kernel();
	}
}
